using System;
using System.Collections.Generic;
using System.Text;
using System.Data;

using VistaDB.DDA;
using VistaDB.Provider;

using NSimpleDB.Service.Contract;
using NSimpleDB.Service.VistaDb.Managers;
using NSimpleDB.Service.VistaDb.QueryProcessing;
using NSimpleDB.Service.VistaDb.Resultsets;

namespace NSimpleDB.Service.VistaDb
{
    //TODO: optimize mapping from domain name to domain id. maybe mirror the domain table in memory. create a class of its own to manage the domain table. needs to be threadsafe!
    //TODO: instrument service and its managers
    public class VistaDbSimpleDBService : ISimpleDBService
    {
        #region globals + ctor
        private const int databaseMaintenanceFrequencyMsec = 2000;

        private SchemaManager schemaMang;
        private DomainManager domainMang;
        private ItemManager itemMang;
        private QueryProcessor qProc;
        private ResultsetPageBuffer rsPageBuf;


        public VistaDbSimpleDBService() { }

        public VistaDbSimpleDBService(string connectionString)
        {
            this.Open(connectionString);
        }
        #endregion

        #region ISimpleDBService Members
        #region connection management
        public void Open(string connectionString)
        {
            this.Close();

            this.schemaMang = new SchemaManager(connectionString);
            this.domainMang = new DomainManager(this.schemaMang);
            this.itemMang = new ItemManager(this.schemaMang, this.domainMang);
            this.qProc = new QueryProcessor(this.schemaMang, this.domainMang);
            this.rsPageBuf = new ResultsetPageBuffer();
        }

        public void Close()
        {
            if (this.schemaMang != null && this.domainMang != null)
            {
                this.domainMang.Dispose();
                this.schemaMang.Dispose();
                this.schemaMang = null;
            }
        }
        #endregion

        #region Domain management
        public void CreateDomain(string domainName)
        {
            this.domainMang.CreateDomain(domainName);
        }


        public void DeleteDomain(string domainName)
        {
            this.domainMang.DeleteDomain(domainName);
        }


        public string[] ListDomains()
        {
            string nextToken;
            return this.ListDomains(int.MaxValue, out nextToken);
        }

        public string[] ListDomains(int maxNumberOfDomains, out string nextToken)
        {
            // get domains
            DataTable tbResultset = this.domainMang.ListDomains();

            // pick maxNumberOfDomains domain names from resultset and push the rest into the page buffer
            List<string> domainNameList = new List<string>();

            PagedResultset rs = this.rsPageBuf.CreatePagedResultset(maxNumberOfDomains);
            int i = 0;
            foreach (DataRow r in tbResultset.Rows)
            {
                if (i < maxNumberOfDomains)
                    domainNameList.Add(r["domainName"].ToString());
                else
                    rs.Add(r["domainName"].ToString());
                i++;
            }

            this.rsPageBuf.AddResultset(rs);
            nextToken = rs.FirstToken;

            return domainNameList.ToArray();
        }

        public string[] ListDomains(ref string nextToken)
        {
            return this.rsPageBuf.GetResultsetPage(nextToken, out nextToken);
        }
        #endregion

        #region Item management
        //TODO: make put async
        //TODO: how to check the number of attr of an item? it can be much more than passed to a singe call to putattr()
        public void PutAttributes(string domainName, string itemName, params ISimpleDBAttribute[] attributes)
        {
            if (attributes != null && attributes.Length > 100)
                throw new SimpleDBException("PutAttributes: Too many attributes ({0}) passed to method! Max. number of attributes per call: 100, max. number of attributes in item: 256.", attributes.Length);
            this.itemMang.PutAttributes(domainName, itemName, attributes);
        }


        public ISimpleDBAttribute[] GetAttributes(string domainName, string itemId, params string[] attributeNames)
        {
            DataTable tbResultset = this.itemMang.GetAttributes(domainName, itemId, attributeNames);

            List<ISimpleDBAttribute> resultset = new List<ISimpleDBAttribute>();
            foreach(DataRow r in tbResultset.Rows)
                resultset.Add(new SimpleDBAttribute(r["attributeName"].ToString(), r["attributeValue"].ToString()));
            return resultset.ToArray();
        }


        public void DeleteAttributes(string domainName, string itemId, params string[] attributeNames)
        {
            List<ISimpleDBAttribute> attributes = new List<ISimpleDBAttribute>();
            foreach (string attributeName in attributeNames)
                attributes.Add(new SimpleDBAttribute(attributeName, ""));
            this.DeleteAttributes(domainName, itemId, attributes.ToArray(), false);
        }

        public void DeleteAttributes(string domainName, string itemId, params ISimpleDBAttribute[] attributes)
        {
            this.DeleteAttributes(domainName, itemId, attributes, true);
        }

        private void DeleteAttributes(string domainName, string itemId, ISimpleDBAttribute[] attributes, bool includeAttributes)
        {
            this.itemMang.DeleteAttributes(domainName, itemId, attributes, includeAttributes);
        }
        #endregion

        #region Querying
        public string[] Query(string domainName, string query)
        {
            string nextToken;
            return this.Query(domainName, query, int.MaxValue, out nextToken);
        }

        //TODO: make query semi-async
        public string[] Query(string domainName, string query, int maxNumberOfItems, out string nextToken)
        {
            DataTable tbResultset = this.qProc.ExecuteQuery(domainName, query);

            List<string> resultset = new List<string>();

            PagedResultset rs = this.rsPageBuf.CreatePagedResultset(maxNumberOfItems);
            int i = 0;
            foreach (DataRow r in tbResultset.Rows)
            {
                if (i < maxNumberOfItems)
                    resultset.Add(r["itemName"].ToString());
                else
                    rs.Add(r["itemName"].ToString());
                i++;
            }

            this.rsPageBuf.AddResultset(rs);
            nextToken = rs.FirstToken;

            return resultset.ToArray();
        }

        public string[] Query(ref string nextToken)
        {
            return this.rsPageBuf.GetResultsetPage(nextToken, out nextToken);
        }
        #endregion
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this.Close();
        }

        #endregion
    }
}
